#region Copyright RenGuiYou. All rights reserved.
//=====================================================
// PandoraFrameWork
// Author:      RenGuiyou
// Feedback: 	mailto:750539605@qq.com
//=====================================================
#endregion
using System.Collections.Generic;
using System.IO;
using Pandora;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace PandoraEditor
{
    public static class AtlasMaker
    {
        #region Property
        private static readonly UISetting m_UISetting = UserSetting.UISetting;
        private static readonly string PATH_TEMP = m_UISetting.path_temp;
        private static readonly string PATH_ATLAS_TP = m_UISetting.path_tp;
        private static Dictionary<string, Vector4> frameMap;
        #endregion

        struct AtlasInfoItem
        {
            public readonly string atlasName;
            public readonly string outputAtlasName;

            public AtlasInfoItem(string name)
            {
                atlasName = name;
                outputAtlasName = atlasName;
                if (outputAtlasName.EndsWith("_0"))
                {
                    outputAtlasName = outputAtlasName.SubstringTo("_0");
                }
            }
        }

        public static void MakeAllUIAtlas()
        {
            string path = m_UISetting.path_atlas;
            string[] allAtlasPath = PdrFileUtil.GetDirsName(path);
            string title = "Make Atlas";
            EditorUtility.DisplayProgressBar(title, "Please Wait......", 0);
            int count = allAtlasPath.Length;
            for (int i = 0; i < count; i++)
            {
                var atlasName = allAtlasPath[i];
                string hint = $"{atlasName} {i + 1}/{count}";
                EditorUtility.DisplayProgressBar(title, hint, (i + 1) * 1.0f / count);
                MakeUICompressAtlas(Path.Combine(path, atlasName));
            }
            EditorUtility.ClearProgressBar();
        }

        public static void MakeUIUnCompressAtlas(Object obj)
        {
            var path = AssetDatabase.GetAssetPath(obj);
            MakeUICompressAtlas(path);
        }

        private static void MakeUICompressAtlas(string path)
        {
            var sourceAtlasName = Path.GetFileNameWithoutExtension(path);
            ClearOldAtlas(sourceAtlasName);
            string outputPath = PATH_ATLAS_TP;
            TextureUtility.ImportUISprite(path);
            var atlasInfoList = MakeAtlas(sourceAtlasName, path);
            foreach (var atlasInfoItem in atlasInfoList)
            {
                frameMap = ReadPackedFrame(atlasInfoItem.atlasName);
                CopyColorTexture(atlasInfoItem, path, outputPath);
                MakeMaterial(atlasInfoItem);
                CreatePrefabAndConfig(atlasInfoItem, outputPath);
            }
        }

        public static void MakeUICompressSingleAtlasNoAlpha(Object obj)
        {
            var path = AssetDatabase.GetAssetPath(obj);
            var atlasName = Path.GetFileNameWithoutExtension(path);
            string outputPath = PATH_ATLAS_TP;
            TextureUtility.ImportUISprite(path);
            var atlasColorPath = PdrCombine.PngColor(PATH_ATLAS_TP, atlasName);
            string sourcePath = PdrFileUtil.GetFilesName(path)[0];
            if (!Directory.Exists(outputPath))
            {
                Directory.CreateDirectory(outputPath);
            }
            TextureUtility.CopyColorTexture(sourcePath, atlasColorPath);
            AssetDatabase.Refresh();
            List<SpriteMetaData> spriteList = new List<SpriteMetaData>();
            SpriteMetaData smd = new SpriteMetaData();
            smd.pivot = new Vector2(0.5f, 0.5f);
            smd.border = Vector2.zero;
            smd.name = Path.GetFileNameWithoutExtension(sourcePath);
            int size = AssetDatabase.LoadAssetAtPath<Texture2D>(sourcePath).width;
            smd.rect = new Rect(0, 0, size, size);
            spriteList.Add(smd);
            TextureUtility.ImportAtlasColor(atlasColorPath, spriteList);
            AtlasInfoItem atlasInfoItem = new AtlasInfoItem(atlasName);
            MakeMaterial(atlasInfoItem);
            CreatePrefabAndConfig(atlasInfoItem, outputPath);
        }

        private static void ClearOldAtlas(string sourceAtlasName)
        {
            for (int i = 0; i < 10; i++)
            {
                string atlasName = $"{sourceAtlasName}_{i}";
                if (i == 0)
                {
                    atlasName = sourceAtlasName;
                }
                var AtlasColorPath = PdrCombine.PngColor(PATH_ATLAS_TP, atlasName);
                var atlasMaterialPath = PdrCombine.Material(PATH_ATLAS_TP, atlasName);
                var atlasBlurMaterialPath = PdrCombine.BlurMaterial(PATH_ATLAS_TP, atlasName);
                string pathPrefab = PdrCombine.Prefab(PATH_ATLAS_TP, atlasName);
                PdrFileUtil.DeleteFile(AtlasColorPath);
                PdrFileUtil.DeleteFile(atlasMaterialPath);
                PdrFileUtil.DeleteFile(atlasBlurMaterialPath);
                PdrFileUtil.DeleteFile(pathPrefab);
            }
            AssetDatabase.Refresh();
        }

        private static List<AtlasInfoItem> GetAtlasInfoList(string name)
        {
            List<AtlasInfoItem> atlasInfoList = new List<AtlasInfoItem>();
            for (int i = 0; i < 999; i++)
            {
                string outputName = $"{name}_{i}";
                if (!File.Exists(PdrCombine.Text(PATH_TEMP, outputName)))
                {
                    return atlasInfoList;
                }
                atlasInfoList.Add(new AtlasInfoItem(outputName));
            }
            return null;
        }

        //创建图集
        private static List<AtlasInfoItem> MakeAtlas(string name, string inputPath)
        {
            string output_path = $"{PATH_TEMP}/{name}";
            // int padding = 0;
            // var builder = PdrCombine.ProjectRoot(UserSetting.CompileSetting.texture_packer_builder);
            // string args = $"{inputPath} {output_path} {padding}";
            // string cmd = $"{builder} {args}";
            // PdrCMDHelper.ProcessPython(cmd);
            string multiChar = "{n}";
            string param = $"--smart-update {inputPath} --data-file {output_path}_{multiChar}.txt --format unity --sheet {output_path}_{multiChar}.png " +
                           $"--max-size {m_UISetting.atlas_max_size} --force-squared --size-constraints POT --multipack --disable-rotation --trim-mode Trim " +
                           $"--trim-margin 0 --extrude 1 --border-padding 0 --shape-padding 0";
            PdrCMDHelper.ProcessCommand("TexturePacker.exe", param);
            AssetDatabase.Refresh();
            var atlasInfoList = GetAtlasInfoList(name);
            // foreach (var atlasInfoItem in atlasInfoList)
            // {
            //     string path = PdrCombine.Png(PATH_TEMP, atlasInfoItem.atlasName);
            //     TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
            //     importer.textureType = TextureImporterType.Default;
            //     importer.alphaSource = TextureImporterAlphaSource.FromInput;
            //     importer.textureCompression = TextureImporterCompression.Uncompressed;
            //     importer.isReadable = true;
            //     importer.maxTextureSize = 2048;
            //     importer.SaveAndReimport();
            //     AssetDatabase.Refresh();
            // }
            return atlasInfoList;
        }

        //Copy Color图
        private static void CopyColorTexture(AtlasInfoItem atlasInfoItem, string inputPath, string outputPath)
        {
            var outputAtlasName = atlasInfoItem.outputAtlasName;
            var atlasColorPath = PdrCombine.PngColor(PATH_ATLAS_TP, outputAtlasName);
            string path = PdrCombine.Png(PATH_TEMP, atlasInfoItem.atlasName);
            if (!Directory.Exists(outputPath))
            {
                Directory.CreateDirectory(outputPath);
            }
            TextureUtility.CopyColorTexture(path, atlasColorPath);
            var sprites = ReadSpriteMetaData(atlasInfoItem.atlasName, inputPath);
            TextureUtility.ImportAtlasColor(atlasColorPath, sprites, false);
            AssetDatabase.Refresh();
        }

        //生成材质
        private static void MakeMaterial(AtlasInfoItem atlasInfoItem)
        {
            var outputAtlasName = atlasInfoItem.outputAtlasName;
            var atlasColorPath = PdrCombine.PngColor(PATH_ATLAS_TP, outputAtlasName);
            var atlasMaterialPath = PdrCombine.Material(PATH_ATLAS_TP, outputAtlasName);
            Texture2D textureColor = AssetDatabase.LoadAssetAtPath<Texture2D>(atlasColorPath);
            var material = new Material(Shader.Find(m_UISetting.shader_normal));
            material.SetTexture(m_UISetting.shader_main_tex, textureColor);
            AssetDatabase.CreateAsset(material, atlasMaterialPath);
        }

        public static void MakeBlurMaterial(Object obj)
        {
            var atlasName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(obj));
            for (int i = 0; i < 20; i++)
            {
                var outputAtlasName = atlasName;
                if (i > 0)
                {
                    outputAtlasName = $"{atlasName}_{i}";
                }
                var atlasColorPath = PdrCombine.PngColor(PATH_ATLAS_TP, outputAtlasName);
                if (!File.Exists(atlasColorPath))
                {
                    return;
                }
                var atlasBlurMaterialPath = PdrCombine.BlurMaterial(PATH_ATLAS_TP, outputAtlasName);
                Shader blurShader = Shader.Find(m_UISetting.shader_blur);
                Texture2D textureColor = AssetDatabase.LoadAssetAtPath<Texture2D>(atlasColorPath);
                var blurMaterial = new Material(blurShader);
                blurMaterial.SetTexture(m_UISetting.shader_main_tex, textureColor);
                AssetDatabase.CreateAsset(blurMaterial, atlasBlurMaterialPath);
                string pathPrefab = PdrCombine.Prefab(PATH_ATLAS_TP, outputAtlasName);
                GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(pathPrefab);
                var go = Object.Instantiate(prefab);
                go.name = outputAtlasName;
                var hierarchy = go.GetComponent<NAtlas>() ?? go.AddComponent<NAtlas>();
                hierarchy.blurMaterial = blurMaterial;
                PrefabUtility.SaveAsPrefabAsset(go, pathPrefab);
                Object.DestroyImmediate(go);
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }
        }

        //创建预制与padding信息
        private static void CreatePrefabAndConfig(AtlasInfoItem atlasInfoItem, string outputPath)
        {
            var outputAtlasName = atlasInfoItem.outputAtlasName;
            var AtlasColorPath = PdrCombine.PngColor(PATH_ATLAS_TP, outputAtlasName);
            var AtlasMaterialPath = PdrCombine.Material(PATH_ATLAS_TP, outputAtlasName);
            if (!File.Exists(AtlasColorPath) || !File.Exists(AtlasMaterialPath))
                return;
            string pathPrefab = PdrCombine.Prefab(outputPath, outputAtlasName);
            Material mat = AssetDatabase.LoadAssetAtPath<Material>(AtlasMaterialPath);
            var objects = AssetDatabase.LoadAllAssetRepresentationsAtPath(AtlasColorPath);
            List<Sprite> sprites = new List<Sprite>();
            List<Vector4> rts = new List<Vector4>();
            foreach (Object obj in objects)
            {
                var sprite = obj as Sprite;
                var key = PdrCombine.Png(sprite.name).ToLower();
                sprites.Add(sprite);
                if (frameMap == null || !frameMap.ContainsKey(key))
                {
                    rts.Add(new Vector4());
                }
                else
                {
                    rts.Add(frameMap[key]);
                }
            }
            GameObject go;
            if (File.Exists(pathPrefab))
            {
                GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(pathPrefab);
                go = Object.Instantiate(prefab);
                go.name = outputAtlasName;
                var hierarchy = AtlasExtend.GetComponent(go, mat);
                AtlasExtend.AddSpriteInfo(hierarchy, sprites);
                PrefabUtility.SaveAsPrefabAsset(go, pathPrefab);
            }
            else
            {
                go = new GameObject(outputAtlasName);
                var hierarchy = AtlasExtend.GetComponent(go, mat);
                AtlasExtend.AddSpriteInfo(hierarchy, sprites);
                PrefabUtility.SaveAsPrefabAsset(go, pathPrefab);
            }
            Object.DestroyImmediate(go);
            bool needCreate = false;
            string paddingPath = m_UISetting.path_atlas_asset;
            AtlasConfig data = AssetDatabase.LoadAssetAtPath<AtlasConfig>(paddingPath);
            if (data == null)
            {
                data = ScriptableObject.CreateInstance<AtlasConfig>();
                needCreate = true;
            }
            if (data.atlasList != null)
            {
                for (int i = data.atlasList.Count - 1; i >= 0; i--)
                {
                    var atlasName = data.atlasList[i].name;
                    if (atlasName == outputAtlasName || IsOldAtlas(atlasName, outputAtlasName))
                    {
                        data.atlasList.RemoveAt(i);
                    }
                }
            }
            List<AtlasConfig.PaddingItem> spriteInfo = new List<AtlasConfig.PaddingItem>();
            for (int i = 0; i < sprites.Count; i++)
            {
                spriteInfo.Add(new AtlasConfig.PaddingItem(sprites[i].name, rts[i]));
            }
            data.AddAtlas(outputAtlasName, spriteInfo);
            if (needCreate)
            {
                AssetDatabase.CreateAsset(data, paddingPath);
            }
            else
            {
                EditorUtility.SetDirty(data);
                AssetDatabase.SaveAssets();
            }
            AssetDatabase.Refresh();
        }

        private static bool IsOldAtlas(string atlasName, string outputAtlasName)
        {
            for (int i = 1; i < 20; i++)
            {
                if (atlasName == $"{outputAtlasName}_{i}")
                {
                    return true;
                }
            }
            return false;
        }

        #region ReadHelper
        //读取SpriteMetaData信息
        private static List<SpriteMetaData> ReadSpriteMetaData(string atlasName, string inputPath)
        {
            var atlasTxtPath = PdrCombine.Text(PATH_TEMP, atlasName);
            string text = PdrFileUtil.ReadTextFromFile(atlasTxtPath);
            List<SpriteMetaData> sprites = TexturePacker.ProcessToSprites(text);
            for (int i = 0; i < sprites.Count; ++i)
            {
                SpriteMetaData data = sprites[i];
                string pathTexture = PdrCombine.Png(inputPath, data.name);
                TextureImporter importer = AssetImporter.GetAtPath(pathTexture) as TextureImporter;
                data.border = importer.spriteBorder;
                if (frameMap.ContainsKey(data.name + ".png"))
                {
                    data.border = importer.spriteBorder - frameMap[data.name + ".png"];
                    data.border.x = data.border.x > 0 ? data.border.x : 0;
                    data.border.y = data.border.y > 0 ? data.border.y : 0;
                    data.border.z = data.border.z > 0 ? data.border.z : 0;
                    data.border.w = data.border.w > 0 ? data.border.w : 0;
                }
                if (data.alignment != 9)
                {
                    data.pivot = importer.spritePivot;
                }
                sprites[i] = data;
            }
            return sprites;
        }

        private static Dictionary<string, Vector4> ReadPackedFrame(string atlasName)
        {
            string text = PdrFileUtil.ReadTextFromFile(PdrCombine.Text(PATH_TEMP, atlasName));
            var frameList = TexturePacker.ProcessToFrames(text);
            Dictionary<string, Vector4> paddingMap = new Dictionary<string, Vector4>();
            foreach (var frame in frameList)
            {
                var rt = Vector4.zero;
                rt.x = frame.spriteSourceSize.x;
                rt.y = frame.sourceSize.y - frame.spriteSourceSize.y - frame.spriteSourceSize.height;
                rt.z = frame.sourceSize.x - frame.spriteSourceSize.x - frame.spriteSourceSize.width;
                rt.w = frame.spriteSourceSize.y;
                paddingMap.Add(frame.name.ToLower(), rt);
            }
            return paddingMap;
        }
        #endregion
    }
}